package com.pig4cloud.pigx.user;

import org.bouncycastle.util.encoders.Hex;
import org.junit.jupiter.api.Test;
import org.web3j.crypto.CipherException;
import org.web3j.crypto.ECDSASignature;
import org.web3j.crypto.Hash;
import org.web3j.crypto.Keys;
import org.web3j.crypto.Sign;
import org.web3j.crypto.Sign.SignatureData;
import org.web3j.protocol.admin.Admin;
import org.web3j.protocol.http.HttpService;
import org.web3j.utils.Numeric;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Arrays;

/**
 * 以太坊签名消息校验工具
 *
 * @date:2022/8/10
 */
public class MetaMaskUtil {
    /**
     * 以太坊自定义的签名消息都以以下字符开头
     * 参考 eth_sign in https://github.com/ethereum/wiki/wiki/JSON-RPC
     */
    static final String PERSONAL_MESSAGE_PREFIX = "\u0019Ethereum Signed Message:\n";

    @Test
    void test() {
        //签名后的数据
//		String signature="0x53ea88d24f4ef8cdcc4bcc843912510b065cd6014c453ff61316c4cd75162f0a38f83a2103da028fb8e5181292ba194b0c8aa21a9ddacdf6783ebfa608889d121c";
        String signature = "0xad83f260e58a18fa37779aefd99a418d7d4e3c4103b6839de0d8a91c21ee8f273c84ca8841e1beffaf5294bdadab34042d48a735cff75179db77cb818b2925641b";
        //签名原文
        String message = "g1x6o6o0p1s8w4t7s2t6f5o5e1";
//		String message = "青年人的责任重大！努力吧...";
//		String message = "Hello Dapp";
        //签名的钱包地址
//		String address="0xc290436b3da897115493a1547B52783c50f0Bef3";
        String address = "0x62311D6D724bECAFb9BE76715c3412c59d67B975";
        Boolean result = validate(signature, message, address);
//		Boolean result = this.isSignatureValid(address, signature, message);
        System.out.println(result);

    }

    /**
     * 对签名消息，原始消息，账号地址三项信息进行认证，判断签名是否有效
     *
     * @param signature
     * @param message
     * @param address
     * @return
     */
    public static boolean validate(String signature, String message, String address) {
        //参考 eth_sign in https://github.com/ethereum/wiki/wiki/JSON-RPC
        // eth_sign
        // The sign method calculates an Ethereum specific signature with:
        //    sign(keccak256("\x19Ethereum Signed Message:\n" + len(message) + message))).
        //
        // By adding a prefix to the message makes the calculated signature recognisable as an Ethereum specific signature.
        // This prevents misuse where a malicious DApp can sign arbitrary data (e.g. transaction) and use the signature to
        // impersonate the victim.
        String prefix = PERSONAL_MESSAGE_PREFIX + message.length();
        byte[] msgHash = Hash.sha3((prefix + message).getBytes());

        byte[] signatureBytes = Numeric.hexStringToByteArray(signature);
        byte v = signatureBytes[64];
        if (v < 27) {
            v += 27;
        }

        SignatureData sd = new SignatureData(
                v,
                Arrays.copyOfRange(signatureBytes, 0, 32),
                Arrays.copyOfRange(signatureBytes, 32, 64));

        System.out.println("sd.getR() = " + Hex.toHexString(sd.getR()));
        System.out.println("sd.getS() = " + Hex.toHexString(sd.getS()));
        System.out.println("sd.getV() = " + Hex.toHexString(sd.getV()));
        Admin web3j = Admin.build(new HttpService());


        String addressRecovered = null;
        boolean match = false;

        // Iterate for each possible key to recover
        for (int i = 0; i < 4; i++) {
            BigInteger publicKey = Sign.recoverFromSignature(
                    (byte) i,
                    new ECDSASignature(new BigInteger(1, sd.getR()), new BigInteger(1, sd.getS())),
                    msgHash);

            if (publicKey != null) {
                addressRecovered = "0x" + Keys.getAddress(publicKey);
                System.out.println("addressRecovered = " + addressRecovered);
                System.out.println("address = " + address);
                System.out.println("addressRecovered.toLowerCase() = " + addressRecovered.toLowerCase());
//				if (addressRecovered.equals(address)) {
                if (addressRecovered.toLowerCase().equals(address.toLowerCase())) {
                    match = true;
                    break;
                }
            }
        }
        return match;
    }

    public void signContractTransaction(String contractAddress, String to, BigInteger nonce,
                                        BigInteger gasPrice, BigInteger gasLimit,
                                        BigDecimal amount, BigDecimal decimal) throws IOException, CipherException {
//		BigDecimal realValue = amount.multiply(decimal);
//
//		Collection.Function function = (Collection.Function) new Function("transfer",
//
//				Arrays.asList(new Address(to), new Uint256(realValue.toBigInteger())),
//
//				Collections.emptyList());
//
//		String data = FunctionEncoder.encode((Function) function);
//
//		RawTransaction rawTransaction = RawTransaction.createTransaction(
//				nonce,
//				gasPrice,
//				gasLimit,
//				contractAddress,
//				data);

//		return signData(rawTransaction, wallet, password);

    }

    public boolean isSignatureValid(final String address, final String signature, final String message) {
        System.out.println("isSignatureValid invoked for Address {} with Signature {} and Message {} ");
        System.out.println("address = " + address);
        System.out.println("signature = " + signature);
        System.out.println("message = " + message);


        final String personalMessagePrefix = "\u0019Ethereum Signed Message:\n";
        boolean match = false;

        final String prefix = personalMessagePrefix + message.length();
        final byte[] msgHash = Hash.sha3((prefix + message).getBytes());
        final byte[] signatureBytes = Numeric.hexStringToByteArray(signature);
        byte v = signatureBytes[64];
        if (v < 27) {
            v += 27;
        }

        final Sign.SignatureData sd = new Sign.SignatureData(v,
                Arrays.copyOfRange(signatureBytes, 0, 32),
                Arrays.copyOfRange(signatureBytes, 32, 64));

        String addressRecovered = null;

        // Iterate for each possible key to recover
        for (int i = 0; i < 4; i++) {
            final BigInteger publicKey = Sign.recoverFromSignature((byte) i, new ECDSASignature(
                    new BigInteger(1, sd.getR()),
                    new BigInteger(1, sd.getS())), msgHash);

            if (publicKey != null) {
                addressRecovered = "0x" + Keys.getAddress(publicKey);
                System.out.println("addressRecovered = " + addressRecovered);
                System.out.println("address = " + address);
                if (addressRecovered.equals(address)) {
                    match = true;
                    break;
                }
            }
        }

        return match;
    }

}
